// ValidationAspects
// Written by Mike Saunders
// Released under Microsoft Public License (Ms-PL)
using System;
using System.Linq;
using System.Reflection;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.InterceptionExtension;
using NUnit.Framework;
using ValidationAspects;
using ValidationAspects.Exceptions;
using ValidationAspects.Sdk;
using ValidationAspects.Unity;

namespace TestValidationAspects.Unity
{
	[TestFixture]
	public class TestInterfaceInterceptionExceptionHierarchy
	{
		private IUnityContainer _container;

		[SetUp]
		public void Setup()
		{
			TestHelpers.ResetFramework();

			_container = new UnityContainer();
			_container.AddNewExtension<Interception>();

			_container.RegisterType<IA, A>();
			_container.RegisterType<IB, B>();

			_container.Configure<Interception>().SetDefaultInterceptorFor<IA>(new TransparentProxyInterceptor()).AddPolicy("ValidationPolicy").AddMatchingRule(new ValidationMatchingRule()).AddCallHandler(
				new ValidationCallHandler());
			_container.Configure<Interception>().SetDefaultInterceptorFor<IB>(new TransparentProxyInterceptor()).AddPolicy("ValidationPolicy").AddMatchingRule(new ValidationMatchingRule()).AddCallHandler(
				new ValidationCallHandler());
		}

		[Test]
		public void Test_Exception_hierachy_for_property_validation()
		{
			// expect
			// C(1)			A
			//  IB			A
			//  C(2)		B
			//   O			B
			//   C(3)		B
			//    B			B
			//    C(4)		B
			//     BV1		B
			//     BV2		B
			//   IB			B

			// C = ValidationContextException

			typeof(IB).AddValidation(new[] {new ValidateIB()});
			typeof(B).AddValidation(new[] {new ValidateB()});
			PropertyInfo propertyB = TypeOf<A>.GetProperty(x => x.B);
			propertyB.AddValidation(new[] {new ValidateIB()});

			IA a = _container.Resolve<IA>();
			IB b = _container.Resolve<IB>();

			try
			{
				a.B = b;
			}
			catch (ValidationContextException contextException1)
			{
				// test nested hierarchy
				MethodInfo method = propertyB.GetSetMethod();
				contextException1.Context.TestValues(typeof(A), propertyB, method, method.GetParameters()[0], new object[] {b});
				Assert.AreEqual(2, contextException1.Exceptions.Count());

				ValidateIBException ibException1 = contextException1.Exceptions.OfType<ValidateIBException>().First();
				ibException1.Context.TestValues(typeof(A), propertyB, method, method.GetParameters()[0], new object[] {b});

				ValidationContextException contextException2 = contextException1.Exceptions.OfType<ValidationContextException>().First();
				contextException2.Context.TestValues(typeof(IB), null, null, null, null);
				Assert.AreEqual(3, contextException2.Exceptions.Count());

				ValidateObjectException objectException = contextException2.Exceptions.OfType<ValidateObjectException>().First();
				objectException.Context.TestValues(typeof(IB), null, null, null, null);

				ValidationContextException contextException3 = contextException2.Exceptions.OfType<ValidationContextException>().First();
				contextException3.Context.TestValues(typeof(B), null, null, null, null);
				Assert.AreEqual(2, contextException3.Exceptions.Count());

				ValidateBException bException = contextException3.Exceptions.OfType<ValidateBException>().First();
				bException.Context.TestValues(typeof(B), null, null, null, null);

				ValidationContextException contextException4 = contextException3.Exceptions.OfType<ValidationContextException>().First();
				contextException4.Context.TestValues(typeof(B), null, null, null, null);
				Assert.AreEqual(2, contextException4.Exceptions.Count());

				ValidateBV1Exception bv1Exception = contextException4.Exceptions.OfType<ValidateBV1Exception>().First();
				bv1Exception.Context.TestValues(typeof(B), null, typeof(B).GetMethod("Validate1"), null, new object[] {b});

				ValidateBV2Exception bv2Exception = contextException4.Exceptions.OfType<ValidateBV2Exception>().First();
				bv2Exception.Context.TestValues(typeof(B), null, typeof(B).GetMethod("Validate2"), null, new object[] {b});

				ValidateIBException ibException2 = contextException2.Exceptions.OfType<ValidateIBException>().First();
				ibException2.Context.TestValues(typeof(IB), null, null, null, null);

				// test flattened
				ValidationException[] exceptions = contextException1.Flatten().ToArray();
				Assert.AreEqual(6, exceptions.Count());
				Assert.AreSame(ibException1, exceptions[0]);
				Assert.AreSame(objectException, exceptions[1]);
				Assert.AreSame(bException, exceptions[2]);
				Assert.AreSame(bv1Exception, exceptions[3]);
				Assert.AreSame(bv2Exception, exceptions[4]);
				Assert.AreSame(ibException2, exceptions[5]);

				Assert.AreEqual(6, contextException1.Messages.Count());

				return;
			}
			Assert.Fail("thrown Exception hierarchy is incorrect.");
		}

		[Test]
		public void Test_Exception_hierachy_for_parameter_validation()
		{
			// expect
			// C(1)			A
			//  C(2)		A
			//   IB			A
			//   C(3)		B
			//    O			B
			//    C(4)		B
			//     B		B
			//     C(5)		B
			//      BV1		B
			//      BV2		B
			//    IB		B

			// C = ValidationContextException

			MethodInfo method = typeof(A).GetMethod("Run");
			ParameterInfo parameter = method.GetParameters()[0];

			typeof(IB).AddValidation(new[] {new ValidateIB()});
			typeof(B).AddValidation(new[] {new ValidateB()});
			parameter.AddValidation(new[] {new ValidateIB()});

			IA a = _container.Resolve<IA>();
			IB b = _container.Resolve<IB>();

			try
			{
				a.Run(b);
			}
			catch (ValidationContextException contextException1)
			{
				// test nested hierarchy
				contextException1.Context.TestValues(typeof(A), null, method, null, new object[] {b});
				Assert.AreEqual(1, contextException1.Exceptions.Count());

				ValidationContextException contextException2 = contextException1.Exceptions.OfType<ValidationContextException>().First();
				contextException2.Context.TestValues(typeof(A), null, method, parameter, new object[] {b});
				Assert.AreEqual(2, contextException2.Exceptions.Count());

				ValidateIBException ibException1 = contextException2.Exceptions.OfType<ValidateIBException>().First();
				ibException1.Context.TestValues(typeof(A), null, method, parameter, new object[] {b});

				ValidationContextException contextException3 = contextException2.Exceptions.OfType<ValidationContextException>().First();
				contextException3.Context.TestValues(typeof(IB), null, null, null, null);
				Assert.AreEqual(3, contextException3.Exceptions.Count());

				ValidateObjectException objectException = contextException3.Exceptions.OfType<ValidateObjectException>().First();
				objectException.Context.TestValues(typeof(IB), null, null, null, null);

				ValidationContextException contextException4 = contextException3.Exceptions.OfType<ValidationContextException>().First();
				contextException4.Context.TestValues(typeof(B), null, null, null, null);
				Assert.AreEqual(2, contextException4.Exceptions.Count());

				ValidateBException bException = contextException4.Exceptions.OfType<ValidateBException>().First();
				bException.Context.TestValues(typeof(B), null, null, null, null);

				ValidationContextException contextException5 = contextException4.Exceptions.OfType<ValidationContextException>().First();
				contextException5.Context.TestValues(typeof(B), null, null, null, null);
				Assert.AreEqual(2, contextException5.Exceptions.Count());

				ValidateBV1Exception bv1Exception = contextException5.Exceptions.OfType<ValidateBV1Exception>().First();
				bv1Exception.Context.TestValues(typeof(B), null, typeof(B).GetMethod("Validate1"), null, new object[] {b});

				ValidateBV2Exception bv2Exception = contextException5.Exceptions.OfType<ValidateBV2Exception>().First();
				bv2Exception.Context.TestValues(typeof(B), null, typeof(B).GetMethod("Validate2"), null, new object[] {b});

				ValidateIBException ibException2 = contextException3.Exceptions.OfType<ValidateIBException>().First();
				ibException2.Context.TestValues(typeof(IB), null, null, null, null);

				// test flattened
				ValidationException[] exceptions = contextException1.Flatten().ToArray();
				Assert.AreEqual(6, exceptions.Count());
				Assert.AreSame(ibException1, exceptions[0]);
				Assert.AreSame(objectException, exceptions[1]);
				Assert.AreSame(bException, exceptions[2]);
				Assert.AreSame(bv1Exception, exceptions[3]);
				Assert.AreSame(bv2Exception, exceptions[4]);
				Assert.AreSame(ibException2, exceptions[5]);

				Assert.AreEqual(6, contextException1.Messages.Count());

				return;
			}
			Assert.Fail("thrown Exception hierarchy is incorrect.");
		}

		#region Mocks
		public interface IA
		{
			IB B { get; set; }

			void Run(IB b);
		}

		public class A : IA
		{
			[ValidateObject]
			public IB B { get; set; }

			public void Run([ValidateObject] IB b) {}
		}

		public interface IB {}

		public class ValidateBV1Exception : ValidationException {}

		public class ValidateBV2Exception : ValidationException {}

		public class ValidateBException : ValidationException {}

		public class ValidateIBException : ValidationException {}

		public class B : IB
		{
			[ValidationMethod]
			public static void Validate1(B b)
			{
				throw new ValidateBV1Exception();
			}

			[ValidationMethod]
			public static void Validate2(B b)
			{
				throw new ValidateBV2Exception();
			}
		}

		public class ValidateIB : ValidatorFactoryBase<IB>
		{
			#region Overrides of ValidatorFactoryBase<IB>
			protected override Action<IB, IValidationContext> ValidateAction
			{
				get { return (v, c) => { throw new ValidateIBException(); }; }
			}
			#endregion
		}

		public class ValidateB : ValidatorFactoryBase<B>
		{
			#region Overrides of ValidatorFactoryBase<IB>
			protected override Action<B, IValidationContext> ValidateAction
			{
				get { return (v, c) => { throw new ValidateBException(); }; }
			}
			#endregion
		}
		#endregion
	}
}